package com.keepc.licence.util;

import de.schlichtherle.license.*;
import de.schlichtherle.xml.GenericCertificate;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import com.keepc.licence.entity.AdditionInfo;
import com.keepc.licence.serverinfo.AbstractServerInfos;
import com.keepc.licence.serverinfo.LinuxServerInfos;
import com.keepc.licence.serverinfo.WindowsServerInfos;

import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = true)
@Slf4j
/**
 * 自定义许可管理器类，扩展自LicenseManager，提供创建、安装和验证许可的功能。
 * 增加额外的服务器硬件信息校验。
 */
public class CustomLicenseManager extends LicenseManager {

    // XML编码
    private static final String XML_CHARSET = "UTF-8";
    // 默认buffer size
    private static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

    /**
     * 构造函数，初始化许可管理器。
     *
     * @param param 许可参数
     */
    public CustomLicenseManager(LicenseParam param) {
        super(param);
    }

    /**
     * 创建许可。
     *
     * @param content 许可内容
     * @param notary  许可公证人
     * @return 加密后的许可密钥
     * @throws Exception 创建许可过程中可能出现的异常
     */
    @Override
    protected synchronized byte[] create(LicenseContent content, LicenseNotary notary)
            throws Exception {
        initialize(content);
        this.validateCreate(content);
        final GenericCertificate certificate = notary.sign(content);
        return getPrivacyGuard().cert2key(certificate);
    }

    /**
     * 安装许可。
     *
     * @param key    许可密钥
     * @param notary 许可公证人
     * @return 安装的许可内容
     * @throws Exception 安装许可过程中可能出现的异常
     */
    @Override
    protected synchronized LicenseContent install(final byte[] key, final LicenseNotary notary)
            throws Exception {
        final GenericCertificate certificate = getPrivacyGuard().key2cert(key);
        notary.verify(certificate);
        final LicenseContent content = (LicenseContent) this.load(certificate.getEncoded());
        this.validate(content);
        setLicenseKey(key);
        setCertificate(certificate);
        return content;
    }

    /**
     * 验证许可。
     *
     * @param notary 许可公证人
     * @return 验证通过的许可内容
     * @throws Exception 验证许可过程中可能出现的异常
     */
    @Override
    protected synchronized LicenseContent verify(final LicenseNotary notary)
            throws Exception {
        // 从偏好设置中加载许可密钥
        final byte[] key = getLicenseKey();
        if (null == key) {
            throw new NoLicenseInstalledException(getLicenseParam().getSubject());
        }
        GenericCertificate certificate = getPrivacyGuard().key2cert(key);
        notary.verify(certificate);
        final LicenseContent content = (LicenseContent) this.load(certificate.getEncoded());
        this.validate(content);
        setCertificate(certificate);

        return content;
    }

    /**
     * 验证创建许可的内容。
     *
     * @param content 许可内容
     * @throws LicenseContentException 许可内容验证失败时抛出的异常
     */
    protected synchronized void validateCreate(final LicenseContent content)
            throws LicenseContentException {
        final Date now = new Date();
        final Date notBefore = content.getNotBefore();
        final Date notAfter = content.getNotAfter();
        if (null != notAfter && now.after(notAfter)) {
            throw new LicenseContentException("证书失效时间不能早于当前时间");
        }
        if (null != notBefore && null != notAfter && notAfter.before(notBefore)) {
            throw new LicenseContentException("证书生效时间不能晚于证书失效时间");
        }
        final String consumerType = content.getConsumerType();
        if (null == consumerType) {
            throw new LicenseContentException("用户类型不能为空");
        }
    }

    /**
     * 验证许可内容。
     *
     * @param content 许可内容
     * @throws LicenseContentException 许可内容验证失败时抛出的异常
     */
    @Override
    protected synchronized void validate(final LicenseContent content)
            throws LicenseContentException {
        // 1. 首先调用父类的validate方法
        super.validate(content);
        // 2. 然后校验自定义的License参数
        // License中可被允许的参数信息
        AdditionInfo expectedCheckModel = (AdditionInfo) content.getExtra();
        // 当前服务器真实的参数信息
        AdditionInfo serverCheckModel = getServerInfos();
        // 有一个对象为空 直接抛异常
        if (expectedCheckModel == null || serverCheckModel == null) {
            throw new LicenseContentException("不能获取服务器硬件信息");
        }
        // 校验IP地址
        if (ipAddressInvalid(expectedCheckModel.getIpAddress(), serverCheckModel.getIpAddress())) {
            throw new LicenseContentException("当前服务器的IP没在授权范围内");
        }
        // 校验Mac地址
        if (ipAddressInvalid(expectedCheckModel.getMacAddress(), serverCheckModel.getMacAddress())) {
            throw new LicenseContentException("当前服务器的Mac地址没在授权范围内");
        }
        // 校验主板序列号
        if (serialInvalid(expectedCheckModel.getMainBoardSerial(), serverCheckModel.getMainBoardSerial())) {
            throw new LicenseContentException("当前服务器的主板序列号没在授权范围内");
        }
        // 校验CPU序列号
        if (serialInvalid(expectedCheckModel.getCpuSerial(), serverCheckModel.getCpuSerial())) {
            throw new LicenseContentException("当前服务器的CPU序列号没在授权范围内");
        }
    }

    /**
     * 从字符串形式的编码中加载对象。
     *
     * @param encoded 编码后的字符串
     * @return 加载的对象
     */
    private Object load(String encoded) {
        BufferedInputStream inputStream = null;
        XMLDecoder decoder = null;
        try {
            inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XML_CHARSET)));
            decoder = new XMLDecoder(new BufferedInputStream(inputStream, DEFAULT_BUFFER_SIZE), null, null);
            return decoder.readObject();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } finally {
            try {
                if (decoder != null) {
                    decoder.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (Exception e) {
                log.error("XMLDecoder解析XML失败", e);
            }
        }
        return null;
    }

    /**
     * 获取服务器信息。
     *
     * @return 服务器的硬件信息
     */
    private AdditionInfo getServerInfos() {
        // 操作系统类型
        String osName = System.getProperty("os.name").toLowerCase();
        AbstractServerInfos abstractServerInfos;

        // 根据不同操作系统类型选择不同的数据获取方法
        if (osName.startsWith("windows")) {
            abstractServerInfos = new WindowsServerInfos();
        } else if (osName.startsWith("linux")) {
            abstractServerInfos = new LinuxServerInfos();
        } else {// 其他服务器类型
            abstractServerInfos = new LinuxServerInfos();
        }
        return abstractServerInfos.getServerInfos();
    }

    /**
     * 判断IP地址是否有效。
     *
     * @param expectedList 预期IP地址列表
     * @param serverList   服务器IP地址列表
     * @return 如果服务器IP地址在预期列表中，则返回false；否则返回true。
     */
    private boolean ipAddressInvalid(List<String> expectedList, List<String> serverList) {
        if (CollectionUtils.isEmpty(expectedList) || CollectionUtils.isEmpty(serverList)) {
            return true;
        }
        for (String expected : expectedList) {
            if (serverList.contains(expected.trim())) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断序列号是否有效。
     *
     * @param expectedSerial 预期序列号
     * @param serverSerial   服务器序列号
     * @return 如果序列号为空或不相等，则认为无效，返回true；否则返回false。
     */
    private boolean serialInvalid(String expectedSerial, String serverSerial) {
        // 为空或者不相等 就认为无效
        return StringUtils.isBlank(expectedSerial) || StringUtils.isBlank(serverSerial)
                || !expectedSerial.equals(serverSerial);
    }
}